275 - Getter and Setter 好的,同学你好!今天我们来学习 C++ 中非常重要的一个概念:Getters 和 Setters。这节课我会带你一步一步理解它们,并学会如何在代码中使用它们。
C++ 课堂:理解 Getters 和 Setters (访问器与修改器) 总结: Getters 和 Setters 是一组成对的成员函数 (member functions),它们分别用于读取(获取)和修改(设置)类 (class) 中私有 (private) 成员变量 (member variables) 的值。这是实现数据封装 (encapsulation) 的关键手段之一,能让我们更好地控制对类内部数据的访问。
1. 详细解释 1.1 为什么需要 Getters 和 Setters? 在面向对象编程中,一个核心思想是封装 (encapsulation) 。这意味着我们通常会将类的成员变量声明为 private
,以防止它们被类外部的代码直接随意修改,从而保护数据的完整性和一致性。
想象一下,你有一个 Cylinder
(圆柱体)类,它有 base_radius_
(底面半径)和 height_
(高)两个私有成员变量。
C++
1 2 3 4 5 6 class Cylinder { private: double base_radius_; double height_; // ... 其他成员 };
如果这些变量是 public
(公有的),那么在类的外部(比如 main
函数中),任何人都可以直接这样写:
C++
1 2 3 4 // 假设 base_radius_ 和 height_ 是 public 的 (不推荐) Cylinder c1; c1.base_radius_ = -5.0; // 半径可以是负数吗?这显然不合理! c1.height_ = 0.0; // 高度为0?可能也不符合预期。
这种直接访问和修改的方式,使得我们无法对赋给成员变量的值进行任何检查或控制。例如,半径不应该是负数。如果成员变量是 private
的,外部代码就不能直接访问它们,这就保护了它们。
但问题来了:如果成员变量是 private
的,我们又确实需要在类的外部读取它们的值,或者在满足某些条件的情况下修改它们的值,该怎么办呢?
答案就是使用 Getters 和 Setters !
1.2 Getters (获取器/访问器)
用途 :Getter 函数用于从类的外部读取 私有成员变量的值。它“获取”数据。
特点 :
通常是 public
的,这样外部才能调用它。
它的返回类型 (return type) 与它要获取的成员变量的类型相同。
通常不接受任何参数 (parameters)。
函数体内部很简单,就是 return
相应的私有成员变量。
一个好的实践是,如果 Getter 不会修改任何成员变量(通常它们不会),应该将它们声明为 const
成员函数。这意味着这个函数可以在一个 const
对象上被调用。
示例 (续上例) :
C++
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 class Cylinder { private: double base_radius_; double height_; public: // 构造函数 (Constructor) Cylinder(double r, double h) : base_radius_(r), height_(h) {} // Getter for base_radius_ double get_base_radius() const { // 注意 const return base_radius_; } // Getter for height_ double get_height() const { // 注意 const return height_; } // ... 其他成员 };
现在,在 main
函数中,我们可以这样做:
C++
1 2 3 4 Cylinder c1(10.0, 5.0); double radius = c1.get_base_radius(); // 正确:通过 getter 获取半径 double h = c1.get_height(); // 正确:通过 getter 获取高度 // c1.base_radius_ = 2.0; // 错误!base_radius_ 是 private 的
1.3 Setters (设置器/修改器)
用途 :Setter 函数用于从类的外部修改 私有成员变量的值。它“设置”数据。
特点 :
通常是 public
的。
它的返回类型通常是 void
,因为它只是修改数据,不需要返回什么。
它通常接受一个参数,该参数的类型与它要设置的成员变量的类型相同。这个参数将是新的值。
函数体内部会将传入的参数赋值给相应的私有成员变量。
非常重要的一点 :Setter 允许我们在赋值之前加入验证逻辑 。这是 Setter 相比直接公开成员变量的一大优势。
示例 (续上例) :
C++
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 class Cylinder { private: double base_radius_; double height_; public: // 构造函数 Cylinder(double r, double h) { // 构造函数中也可以使用setter进行初始化,以复用验证逻辑 set_base_radius(r); set_height(h); } // Getter for base_radius_ double get_base_radius() const { return base_radius_; } // Getter for height_ double get_height() const { return height_; } // Setter for base_radius_ void set_base_radius(double r_param) { if (r_param > 0) { // 验证逻辑:半径必须大于0 base_radius_ = r_param; } else { // 可以选择报错、设置一个默认值或什么都不做 // 这里我们简单地不修改,或者可以抛出异常 // std::cerr << "Error: Radius must be positive." << std::endl; base_radius_ = 1.0; // 或者赋一个默认值 } } // Setter for height_ void set_height(double h_param) { if (h_param > 0) { // 验证逻辑:高度必须大于0 height_ = h_param; } else { // std::cerr << "Error: Height must be positive." << std::endl; height_ = 1.0; // 或者赋一个默认值 } } double volume() const { return 3.1415926535 * base_radius_ * base_radius_ * height_; } };
现在,在 main
函数中,我们可以这样做:
C++
1 2 3 4 5 6 7 8 Cylinder c1(10.0, 5.0); std::cout << "Initial radius: " << c1.get_base_radius() << std::endl; c1.set_base_radius(12.0); // 正确:通过 setter 修改半径 std::cout << "New radius: " << c1.get_base_radius() << std::endl; c1.set_base_radius(-2.0); // 尝试设置一个无效的半径 std::cout << "Radius after invalid set: " << c1.get_base_radius() << std::endl; // 半径会是1.0(默认值)或保持不变,取决于setter的实现
1.4 命名约定 虽然不是强制的,但 Getter 通常以 get 开头,后跟成员变量名(首字母大写),例如 get_base_radius() 或 getBaseRadius()。
Setter 通常以 set 开头,后跟成员变量名(首字母大写),例如 set_base_radius(double r) 或 setBaseRadius(double r)。
这种约定使得代码更易读。
1.5 访问控制 (public
Vs private
) 正如视频中提到的,Getters 和 Setters 必须是 public
的,这样它们才能从类的外部被调用。如果它们被声明为 private
,那么它们就只能在类的内部被其他成员函数调用,这对于从外部控制数据的目的来说是无效的,并且会导致编译器错误,就像视频中演示的那样。
public: 关键字之后声明的所有成员(直到下一个访问说明符如 private: 或 protected: 或类定义结束)都是公有的。
private: 关键字之后声明的所有成员都是私有的。
2. 代码示例 (Cylinder 类) 让我们来看一个完整的 Cylinder
类的例子,它使用了构造函数、Getters、Setters 和一个计算体积的方法 (method)。
C++
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 #include <iostream> #include <cmath> // 为了 M_PI (有些编译器需要 #define _USE_MATH_DEFINES) // 如果 M_PI 未定义 (例如在 MSVC 中且未 #define _USE_MATH_DEFINES) #ifndef M_PI #define M_PI (3.14159265358979323846) #endif class Cylinder { private: double base_radius_; // 私有成员变量 (private member variable) double height_; // 私有成员变量 public: // 构造函数 (Constructor) Cylinder(double r_param, double h_param) { // 在构造函数中使用 setters 可以复用验证逻辑 set_base_radius(r_param); set_height(h_param); std::cout << "Cylinder object created with radius " << base_radius_ << " and height " << height_ << std::endl; } // 默认构造函数 Cylinder() : base_radius_(1.0), height_(1.0) { std::cout << "Default Cylinder object created with radius 1.0 and height 1.0" << std::endl; } // Getter for base_radius_ double get_base_radius() const { // const 表示此方法不会修改对象的状态 return base_radius_; } // Setter for base_radius_ // 参数 (parameter) : rad - a double representing the new radius void set_base_radius(double rad) { if (rad > 0) { base_radius_ = rad; } else { std::cout << "Invalid radius: " << rad << ". Radius must be positive. Setting to 1.0." << std::endl; base_radius_ = 1.0; // 设置一个默认的有效值 } } // Getter for height_ double get_height() const { return height_; } // Setter for height_ // 参数 : h - a double representing the new height void set_height(double h) { if (h > 0) { height_ = h; } else { std::cout << "Invalid height: " << h << ". Height must be positive. Setting to 1.0." << std::endl; height_ = 1.0; // 设置一个默认的有效值 } } // Method to calculate volume double volume() const { return M_PI * base_radius_ * base_radius_ * height_; } }; int main() { // 创建一个 Cylinder 对象 (object/instance) Cylinder cylinder1(10.0, 2.0); std::cout << "Initial volume: " << cylinder1.volume() << std::endl; std::cout << "Initial radius (via getter): " << cylinder1.get_base_radius() << std::endl; std::cout << "Initial height (via getter): " << cylinder1.get_height() << std::endl; std::cout << "\n--- Modifying cylinder1 ---" << std::endl; // 使用 setters 修改成员变量 cylinder1.set_base_radius(5.0); cylinder1.set_height(3.0); std::cout << "New radius (via getter): " << cylinder1.get_base_radius() << std::endl; std::cout << "New height (via getter): " << cylinder1.get_height() << std::endl; std::cout << "Volume after modification: " << cylinder1.volume() << std::endl; std::cout << "\n--- Attempting invalid modifications ---" << std::endl; cylinder1.set_base_radius(-2.0); // 尝试设置无效的半径 cylinder1.set_height(0); // 尝试设置无效的高度 std::cout << "Radius after invalid set: " << cylinder1.get_base_radius() << std::endl; std::cout << "Height after invalid set: " << cylinder1.get_height() << std::endl; std::cout << "Volume after invalid attempts: " << cylinder1.volume() << std::endl; // 尝试直接访问 (会导致编译错误,因为它们是 private) // cylinder1.base_radius_ = 7.0; // 错误: 'double Cylinder::base_radius_' is private // double r = cylinder1.height_; // 错误: 'double Cylinder::height_' is private // 使用 const 对象调用 const getter const Cylinder cylinder2(3.0, 4.0); std::cout << "\n--- Constant Cylinder ---" << std::endl; std::cout << "Const cylinder radius: " << cylinder2.get_base_radius() << std::endl; std::cout << "Const cylinder height: " << cylinder2.get_height() << std::endl; std::cout << "Const cylinder volume: " << cylinder2.volume() << std::endl; // cylinder2.set_base_radius(5.0); // 错误! 不能在 const 对象上调用非 const 方法 (setter) return 0; }
编译和运行上述代码,你会看到:
对象创建时构造函数被调用。
volume()
函数根据初始值计算体积。
通过 set_base_radius()
和 set_height()
修改了对象的属性后,volume()
计算出的值也相应改变。
当你尝试用 set_base_radius(-2.0)
设置一个无效值时,setter
函数内的逻辑会阻止无效赋值,并可能设置一个默认值,从而保护了对象的内部状态。
const
对象只能调用 const
成员函数 (比如我们的 getters 和 volume()
方法)。
3. 问答卡片 (QA Flash Cards)
问: 什么是 Getter?它有什么作用?
答: Getter 是一个公有成员函数,用于从类外部安全地读取私有成员变量的值。它通常以 get 开头,返回成员变量的值,并且最好声明为 const。
问: 什么是 Setter?它有什么作用?
答: Setter 是一个公有成员函数,用于从类外部安全地修改私有成员变量的值。它通常以 set 开头,接受一个参数作为新值,返回类型通常为 void。Setter 可以在赋值前进行数据验证。
问: 为什么不直接将成员变量设为 public,而是使用 Getters 和 Setters?
答: 为了实现封装。将成员变量设为 private 并通过 public 的 Getters/Setters 访问,可以:
控制访问级别 :只提供 Getter 不提供 Setter,可以创建只读属性。
数据验证 :Setter 可以在修改数据前检查数据是否有效。
隐藏实现细节 :类的内部实现可以改变,只要 Getters/Setters 的接口不变,外部代码就不受影响。
灵活性 :未来可能需要在获取或设置值时执行额外操作(如日志记录、通知等)。
问: Getter 和 Setter 函数通常声明在类的哪个访问区域?
答: public 区域,这样它们才能从类的外部被调用。
问: Getter 函数通常应该标记为什么,以表明它不修改类的状态?
答: const。
4. 常见误解或易犯错误
将 Getters/Setters 声明为 private
:
误解 :以为所有成员函数都可以随意设置访问权限。
后果 :如果 Getters/Setters 是 private
的,它们就不能从类外部被调用,失去了其作为公共接口的意义,会导致编译错误。
Getter 函数修改了成员变量的值 :
误解 :Getter 只是一个普通函数,可以在里面做任何事。
后果 :Getter 的设计目的是“获取”数据,不应该有副作用,特别是修改对象状态。这违反了“最少意外原则”。正确的做法是将其声明为 const
。
Setter 函数忘记接收参数,或者参数类型不匹配 :
误解 :写函数时马虎。
后果 :Setter 需要一个参数来指定新的值。如果忘记参数或类型不符,会导致编译错误。
忘记调用函数时的括号 ()
:
错误 :double r = cylinder.get_base_radius;
(缺少 ()
)
正确 :double r = cylinder.get_base_radius();
后果 :如果是获取函数地址(在某些情况下可能),而不是调用函数,会导致逻辑错误或编译错误。
认为 Getter 返回的是原始数据的引用 (reference),除非明确指出 :
误解 :以为 get_base_radius()
返回的就是 base_radius_
本身。
后果 :默认情况下,Getter 按值返回 (return by value),这意味着返回的是成员变量的一个副本 (copy) 。如果想返回引用(需要谨慎使用),函数签名会是 double& get_base_radius()
。视频中的例子是按值返回,这是最常见和安全的方式。
在 Setter 中未进行任何数据验证 :
后果 :虽然技术上可行,但这样做就失去了 Setter 的一个主要优势。如果 Setter 只是简单赋值 member_ = value;
,那它和直接公开成员变量的区别就小了很多(尽管仍然提供了未来添加逻辑的可能)。
5. 编码练习 现在,我们来做一个小练习。下面有一个 Rectangle
(矩形)类,它有两个私有成员变量:width_
(宽度) 和 height_
(高度)。请你为这个类完成 Getters 和 Setters,并在 main
函数中测试它们。
C++
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 #include <iostream> class Rectangle { private: double width_; double height_; public: // 构造函数 Rectangle(double w, double h) { // TODO: 使用你将要编写的 setters 来初始化 width_ 和 height_ // 确保宽度和高度是正数,如果不是,则都设置为 1.0 set_width(w); set_height(h); std::cout << "Rectangle created with width: " << width_ << " and height: " << height_ << std::endl; } // Getter for width_ // TODO: 实现 get_width() 方法 // 它应该返回 width_ 的值,并且是一个 const 方法 double get_width() const { // 请在此处填入代码 return 0.0; // 这是一个占位符,你需要修改它 } // Setter for width_ // TODO: 实现 set_width() 方法 // 它应该接收一个 double 类型的参数 new_width // 如果 new_width 大于 0,则将其赋值给 width_ // 否则,打印错误消息并将 width_ 设置为 1.0 void set_width(double new_width) { // 请在此处填入代码 std::cout << "Placeholder for set_width. Width not set." << std::endl; // 这是一个占位符 } // Getter for height_ // TODO: 实现 get_height() 方法 // 它应该返回 height_ 的值,并且是一个 const 方法 double get_height() const { // 请在此处填入代码 return 0.0; // 这是一个占位符,你需要修改它 } // Setter for height_ // TODO: 实现 set_height() 方法 // 它应该接收一个 double 类型的参数 new_height // 如果 new_height 大于 0,则将其赋值给 height_ // 否则,打印错误消息并将 height_ 设置为 1.0 void set_height(double new_height) { // 请在此处填入代码 std::cout << "Placeholder for set_height. Height not set." << std::endl; // 这是一个占位符 } // 计算面积的方法 double area() const { return width_ * height_; } }; int main() { std::cout << "--- Creating r1 (valid values) ---" << std::endl; Rectangle r1(10.0, 5.0); // 应该使用 setters 初始化 std::cout << "r1 initial width: " << r1.get_width() << std::endl; std::cout << "r1 initial height: " << r1.get_height() << std::endl; std::cout << "r1 initial area: " << r1.area() << std::endl; std::cout << "\n--- Modifying r1 ---" << std::endl; r1.set_width(7.0); r1.set_height(3.0); std::cout << "r1 new width: " << r1.get_width() << std::endl; std::cout << "r1 new height: " << r1.get_height() << std::endl; std::cout << "r1 new area: " << r1.area() << std::endl; std::cout << "\n--- Creating r2 (invalid values for constructor) ---" << std::endl; Rectangle r2(-2.0, 0.0); // 构造函数中的 setters 应该处理这些无效值 std::cout << "r2 initial width: " << r2.get_width() << std::endl; std::cout << "r2 initial height: " << r2.get_height() << std::endl; std::cout << "r2 initial area: " << r2.area() << std::endl; std::cout << "\n--- Attempting invalid set on r1 ---" << std::endl; r1.set_width(-5.0); std::cout << "r1 width after invalid set: " << r1.get_width() << std::endl; std::cout << "r1 area after invalid width set: " << r1.area() << std::endl; return 0; }
请你将上面代码中标记为 // 请在此处填入代码
和 // TODO:
的部分补充完整。完成后,将你的代码粘贴给我,我会帮你检查并给出反馈!加油!
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 #include <iostream> class Rectangle {private : double width_; double height_; public : Rectangle(double w, double h) { set_width(w); set_height(h); std ::cout << "Rectangle created with width: " << width_ << " and height: " << height_ << std ::endl ; } double get_width () const { return width_; } void set_width (double new_width) { if (new_width > 0 ) { width_ = new_width; } else { width_ = 1 ; std ::cout << "Width Not set. Width should be greater than 0. Width is set to default 1.0." << std ::endl ; } } double get_height () const { return height_; } void set_height (double new_height) { if (new_height > 0 ) { height_ = new_height; } else { height_ = 1 ; std ::cout << "Height Not set. Height should be greater than 0. Height is set to default 1.0." << std ::endl ; } } double area () const { return width_ * height_; } }; int main () { std ::cout << "--- Creating r1 (valid values) ---" << std ::endl ; Rectangle r1 (10.0 , 5.0 ) ; std ::cout << "r1 initial width: " << r1.get_width() << std ::endl ; std ::cout << "r1 initial height: " << r1.get_height() << std ::endl ; std ::cout << "r1 initial area: " << r1.area() << std ::endl ; std ::cout << "\n--- Modifying r1 ---" << std ::endl ; r1.set_width(7.0 ); r1.set_height(3.0 ); std ::cout << "r1 new width: " << r1.get_width() << std ::endl ; std ::cout << "r1 new height: " << r1.get_height() << std ::endl ; std ::cout << "r1 new area: " << r1.area() << std ::endl ; std ::cout << "\n--- Creating r2 (invalid values for constructor) ---" << std ::endl ; Rectangle r2 (-2.0 , 0.0 ) ; std ::cout << "r2 initial width: " << r2.get_width() << std ::endl ; std ::cout << "r2 initial height: " << r2.get_height() << std ::endl ; std ::cout << "r2 initial area: " << r2.area() << std ::endl ; std ::cout << "\n--- Attempting invalid set on r1 ---" << std ::endl ; r1.set_width(-5.0 ); std ::cout << "r1 width after invalid set: " << r1.get_width() << std ::endl ; std ::cout << "r1 area after invalid width set: " << r1.area() << std ::endl ; return 0 ; }
太棒了!你做得非常好!很高兴看到你把练习代码补充完整了。让我们一起来看看你的实现。
7. 代码评估与解答 评估你的解决方案 整体评价:优秀! (Excellent!)
你非常出色地完成了 Getters 和 Setters 的实现,并且正确地在 Setters 中加入了数据验证逻辑,也在构造函数中复用了 Setters。const
的使用也是正确的。这表明你已经很好地理解了这一节课的核心概念。
具体亮点:
Getters (get_width
, get_height
) :
正确返回了私有成员变量 width_
和 height_
。
正确地将这两个方法标记为了 const
,表明它们不会修改对象状态。完美!
Setters (set_width
, set_height
) :
正确接收了参数。
核心的验证逻辑 if (new_value > 0)
非常清晰且正确。
当输入无效时,你将成员变量设置为了默认值 1
,并打印了提示信息。这符合练习的要求。
构造函数 (Rectangle(double w, double h)
) :
你正确地调用了 set_width(w);
和 set_height(h);
来初始化成员变量。这是一个很好的实践,因为它复用了 Setters 中的验证逻辑,避免了代码重复。
一些小建议(锦上添花):
浮点数常量 :在 Setters 中,当输入无效时,你将 width_
和 height_
设置为 1
。因为 width_
和 height_
是 double
类型,更明确(也更符合类型一致性)的写法是 1.0
。虽然编译器通常会自动转换,但显式写出 1.0
可以让代码意图更清晰。
错误/提示信息 :你打印的提示信息 “Width Not set…” 或 “Height Not set…” 稍微有点歧义,因为实际上你是将它们设置为默认值 1.0
了。可以考虑修改为类似:”Invalid width provided. Width must be positive. Setting to default 1.0.” 这样用户能更准确地理解发生了什么。
这些只是非常小的建议,你的核心逻辑和实现都是非常棒的!
参考答案 下面是根据你的实现和我的小建议调整后的 Rectangle
类代码部分:
C++
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 #include <iostream> class Rectangle { private: double width_; double height_; public: // 构造函数 Rectangle(double w, double h) { set_width(w); // 使用 setter 初始化,包含验证逻辑 set_height(h); // 使用 setter 初始化,包含验证逻辑 // 构造函数中的这条输出可以保留用于学习,实际项目中可能会移除 std::cout << "Rectangle created with width: " << width_ << " and height: " << height_ << std::endl; } // Getter for width_ double get_width() const { return width_; } // Setter for width_ void set_width(double new_width) { if (new_width > 0) { width_ = new_width; } else { std::cout << "Invalid width provided: " << new_width << ". Width must be positive. Setting to default 1.0." << std::endl; width_ = 1.0; // 使用 1.0 强调是 double 类型 } } // Getter for height_ double get_height() const { return height_; } // Setter for height_ void set_height(double new_height) { if (new_height > 0) { height_ = new_height; } else { std::cout << "Invalid height provided: " << new_height << ". Height must be positive. Setting to default 1.0." << std::endl; height_ = 1.0; // 使用 1.0 强调是 double 类型 } } // 计算面积的方法 double area() const { return width_ * height_; } }; // main 函数与之前练习中的相同,这里不再重复,用你的 main 函数测试即可 int main() { std::cout << "--- Creating r1 (valid values) ---" << std::endl; Rectangle r1(10.0, 5.0); std::cout << "r1 initial width: " << r1.get_width() << std::endl; std::cout << "r1 initial height: " << r1.get_height() << std::endl; std::cout << "r1 initial area: " << r1.area() << std::endl; std::cout << "\n--- Modifying r1 ---" << std::endl; r1.set_width(7.0); r1.set_height(3.0); std::cout << "r1 new width: " << r1.get_width() << std::endl; std::cout << "r1 new height: " << r1.get_height() << std::endl; std::cout << "r1 new area: " << r1.area() << std::endl; std::cout << "\n--- Creating r2 (invalid values for constructor) ---" << std::endl; Rectangle r2(-2.0, 0.0); std::cout << "r2 initial width: " << r2.get_width() << std::endl; std::cout << "r2 initial height: " << r2.get_height() << std::endl; std::cout << "r2 initial area: " << r2.area() << std::endl; std::cout << "\n--- Attempting invalid set on r1 ---" << std::endl; r1.set_width(-5.0); std::cout << "r1 width after invalid set: " << r1.get_width() << std::endl; std::cout << "r1 area after invalid width set: " << r1.area() << std::endl; return 0; }
当你运行这段代码时,你会看到 main
函数中的测试用例会按照预期工作,包括对无效输入的处理。
8. 扩展思考与进阶问题 为了帮助你更深入地理解和应用这些知识,可以思考以下问题:
更灵活的验证 :当前的 Setter 在验证失败时会将值设为 1.0
。如果希望调用者知道设置操作是否成功,Setter 可以返回一个 bool
值(例如 true
代表成功,false
代表失败)。你觉得这样做有什么好处和坏处?如何修改代码来实现这个功能?
C++
1 2 // 比如: // bool set_width(double new_width);
只读属性 :如果一个成员变量(比如 id_
)在对象创建后就不允许被修改,你该如何设计它的 Getter 和 Setter?(提示:可能不需要 Setter)
链式调用 (Method Chaining) :有些编程风格喜欢链式调用 Setter,例如:rectangle.set_width(10.0).set_height(5.0);
。为了实现这种效果,Setter 函数的返回类型应该是什么?(提示:通常返回对象的引用 *this
)。尝试修改 set_width
和 set_height
以支持链式调用。
错误处理机制 :在 Setter 中直接使用 std::cout
打印错误信息对于学习和简单程序是可行的。但在大型应用程序中,通常会使用更健壮的错误处理机制,比如抛出异常 (throwing exceptions) 。你可以简单了解一下 C++ 中的 try-catch
和 throw
是如何工作的。
Getter 返回引用 :通常 Getter 按值返回成员变量的副本。在某些特定情况下,Getter 也可以返回成员变量的引用(例如 double& get_width()
)或常量引用(const double& get_width() const
)。这样做有什么潜在的好处和风险?(提示:性能 vs. 封装性)。
这些问题可以帮助你从不同角度思考 Getters 和 Setters 的设计和使用。不要有压力,选择你感兴趣的一两个点去探索一下,会对你的 C++ 学习非常有益!
你今天的表现非常棒!继续保持这种学习的热情和细致的态度!如果对这些扩展问题有任何想法或者尝试了修改,欢迎随时与我分享和讨论。